package tmt.usercenter.web.controller;

import com.tmt.datatable.TableRequest;
import com.tmt.datatable.TableResponse;
import com.tmt.datatable.editor.IFileDataHolder;
import com.tmt.fileservice.FileSaveMode;
import com.tmt.fileservice.IFileDataResolver;
import com.tmt.fileservice.IFileService;
import com.tmt.fileservice.exception.FileValidateException;
import com.tmt.helper.PictureRotateHelper;
import com.tmt.helper.RandomCodeHelper;
import com.tmt.sm.impl.ReturnMode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import tmt.usercenter.web.controller.bean.WorkFlowManager;
import tmt.usercenter.web.controller.validtor.UserInfoValidator;
import tmt.usercenter.web.domain.*;
import tmt.usercenter.web.domain.enums.FuncPointType;
import tmt.usercenter.web.domain.security.UserDetailsImpl;
import tmt.usercenter.web.plugin.filesevice.Base64DataResolver;
import tmt.usercenter.web.service.*;
import tmt.usercenter.web.vo.HttpResult;
import tmt.usercenter.web.vo.Notify;
import tmt.usercenter.web.vo.ScopeVo;
import tmt.usercenter.web.vo.user.MobileModifyForm;
import tmt.usercenter.web.vo.user.PasswordVo;
import tmt.usercenter.web.vo.user.UserInfoVo;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Stark
 */
@Controller
@PreAuthorize("isAuthenticated()")
public class UserController extends BaseFrontController {

    @Autowired
    private TmtUserService userService;
    @Autowired
    private IFileService fileService;
    @Autowired
    private ClientSystemService clientService;
    @Autowired
    private TmtApprovalService approvalService;
    @Autowired
    private UserInfoConfigService uiConfigService;
    @Autowired
    private UserInfoValidator rgValidator;
    @Autowired
    private SmHistoryService smHistoryService;
    @Autowired
    private ClientSystemService clientSystemService;

    @Autowired
    private LoginAttemptService loginAttemptService;

    private static final String EMAIL_VALIDATE_FLOW = "redirect:/email/flow";
    private static final String EMAIL_VALIDATE_PAGE = "redirect:/email/validate";


    @InitBinder("user")
    @Override
    protected void initBinder(WebDataBinder binder) {
        super.initBinder(binder);
        binder.addValidators(rgValidator);
    }


    @RequestMapping(value = "/information", method = RequestMethod.GET)
    public String editInfo(Model model, HttpServletRequest request,
                           @AuthenticationPrincipal(expression = "userId") Long userId,
                           @AuthenticationPrincipal(expression = "clientId") String clientId) {
        TmtUser tmtUser = userService.findById(userId);

        if (StringUtils.isEmpty(clientId))
            clientId = getClientId(request);

        UserInfoConfig config = uiConfigService.findByOriginSystemClientId(clientId, true);

        List<String> unmodifiedList = config.getDisableUpdate();

        model.addAttribute("disabledFields", unmodifiedList);
        model.addAttribute("user", UserInfoVo.from(tmtUser));

        return "user/user_info";
    }

    @RequestMapping(value = "/information", method = RequestMethod.POST)
    public String editInfo(@Valid @ModelAttribute("user") UserInfoVo userInfoVo, BindingResult result, Model model, HttpServletRequest request,
                           @AuthenticationPrincipal(expression = "username") String username) {
        TmtUser user = userService.findByUsername(username);

        String clientId = getClientId(request);
        UserInfoConfig config = uiConfigService.findByOriginSystemClientId(clientId, true);
        List<String> unmodifiedList = config.getDisableUpdate();

        if (result.hasErrors()) {
            model.addAttribute("user", userInfoVo);
            model.addAttribute("disabledFields", unmodifiedList);
            return "user/user_info";
        }

        if (!unmodifiedList.contains(UserInfoVo.NICKNAME) && !StringUtils.isEmpty(userInfoVo.getNickname())) {
            user.setNickname(userInfoVo.getNickname());
        }

        if (!unmodifiedList.contains(UserInfoVo.REAL_NAME) && !StringUtils.isEmpty(userInfoVo.getNickname())) {
            user.setRealName(userInfoVo.getRealName());
        }

        if (!unmodifiedList.contains(UserInfoVo.CARD_TYPE) && userInfoVo.getCardType() != null) {
            user.setCardType(userInfoVo.getCardType());
        }

        if (!unmodifiedList.contains(UserInfoVo.ID_CARD_NUM) && !StringUtils.isEmpty(userInfoVo.getIdCardNum())) {
            user.setIdCardNum(userInfoVo.getIdCardNum());
        }

        if (!unmodifiedList.contains(UserInfoVo.BIRTHDAY) && userInfoVo.getBirthday() != null) {
            user.setBirthday(userInfoVo.getBirthday());
        }

        user = userService.updateUserInfo(user);


        model.addAttribute("user", UserInfoVo.from(user));
        model.addAttribute("disabledFields", unmodifiedList);
        notify(model, Notify.notify(Notify.Level.SUCCESS, " 用户信息修改成功", ""));

        return "user/user_info";
    }

    @RequestMapping("/edit/phone")
    public String editPhone() {
        return "";
    }

    @ResponseBody
    @RequestMapping("/edit/avatar")
    public HttpResult editAvatar(HttpServletRequest request, @RequestParam("avatar") String avatarStr,
                                 @AuthenticationPrincipal(expression = "username") String username) throws FileValidateException {
        TmtUser currentUser = userService.findByUsername(username);

        try {
            IFileDataHolder holder = new Base64DataResolver(avatarStr);
            holder.setFieldName("avatar");
            String filePath = fileService.getFileDispatcher().getNewFilePath(TmtUser.class, currentUser.getId(), holder.getFieldName(),
                    holder.getFileName(), FileSaveMode.TEMPARARY);
            fileService.save(filePath, (IFileDataResolver) holder);

            //图片旋转检测和处理。
            PictureRotateHelper.tryRotatePicture(filePath);

            //更新用户头像。
            currentUser = userService.updateAvatar(currentUser.getId(), holder.getFileName());

            String url = fileService.getFileDispatcher().getFileUrl(currentUser, holder.getFieldName(), FileSaveMode.PERSIST);
            currentUser.setAvatar(url);

            //更新当前登录用户头像属性。
            userService.refreshPrincipal(currentUser);
        } catch (Exception ex) {
            HttpResult.success(messageSourceAccessor.getMessage("process.failure"), currentUser.getAvatar());
        }

        return HttpResult.success(messageSourceAccessor.getMessage("process.success"), currentUser.getAvatar());
    }

    @RequestMapping("/contact")
    public String contactIndex(@AuthenticationPrincipal UserDetailsImpl userDetails, Model model) {
        TmtUser currentUser = userService.findByUsername(userDetails.getUsername());
        model.addAttribute("currentUser", currentUser);
        return "user/contact";
    }

    @GetMapping("/contact/mobile")
    public String contactMobile(Model model, HttpServletRequest request,
                                @AuthenticationPrincipal(expression = "userId") Long userId,
                                @AuthenticationPrincipal(expression = "clientId") String clientId) {
        model.addAttribute("model", new MobileModifyForm());
        return "user/contact_mobile";
    }

    @PostMapping("/contact/mobile")
    public String changeMobile(Model model, HttpServletRequest request,
                               @AuthenticationPrincipal(expression = "userId") Long userId,
                               @AuthenticationPrincipal(expression = "clientId") String clientId,
                               @Valid @ModelAttribute("model") MobileModifyForm mobileModifyForm,
                               BindingResult result
    ) {
        String errorPath = "user/contact_mobile";
        if (result.hasErrors()) {
            model.addAttribute("model", mobileModifyForm);
            return errorPath;
        }
        TmtUser user = userService.findById(userId);
        if (user == null || !Objects.equals(user.getMobile(), mobileModifyForm.getMobile())) {
            result.addError(new FieldError("model", "mobile", "手机号码不匹配"));
            return errorPath;
        }

        SmHistory sm1 = smHistoryService.findLastValidByToMobileAndFuncPoint(mobileModifyForm.getMobile(), FuncPointType.UPDATE_MOBILE);
        if (sm1 == null || !sm1.getContent().contains(mobileModifyForm.getMobileCode())) {
            result.addError(new FieldError("model", "mobileCode", "验证码错误"));
            return errorPath;
        }

        SmHistory sm2 = smHistoryService.findLastValidByToMobileAndFuncPoint(mobileModifyForm.getNewMobile(), FuncPointType.UPDATE_MOBILE);
        if (sm2 == null || !sm2.getContent().contains(mobileModifyForm.getNewMobileCode())) {
            result.addError(new FieldError("model", "newMobileCode", "验证码错误"));
            return errorPath;
        }

        try {
            uiConfigService.validateMobileRule(mobileModifyForm.getNewMobile(), userId, clientId);
        } catch (Exception ex) {
            result.addError(new FieldError("model", "newMobileCode", ex.getMessage()));
            return errorPath;
        }

        userService.updateMobile(userId, mobileModifyForm.getNewMobile());
        notify(model, Notify.notify(Notify.Level.SUCCESS, "操作成功", "手机号码已成功修改"));

        return "redirect:/contact";
    }

    @ResponseBody
    @PostMapping(value = "/contact/mobile/change/{mobile}")
    public HttpResult sendCodeToMobile(@AuthenticationPrincipal(expression = "userId") Long userId,
                                       @AuthenticationPrincipal(expression = "clientId") String clientId,
                                       @PathVariable String mobile) {
        TmtUser user = userService.findById(userId);
        if (!Objects.equals(user.getMobile(), mobile)) {
            return HttpResult.failure("手机号码和原号码不一致");
        }

        ClientSystem clientSystem = clientSystemService.findByClientId(clientId);
        String code = RandomCodeHelper.getNumberCode(6);
        String content = MessageFormat.format("{0}手机修改验证码：{1}", clientSystem.getName(), code);

        HttpResult result = smHistoryService.sendSm(clientId,
                mobile, content, FuncPointType.UPDATE_MOBILE, false, ReturnMode.SYNC_MODE);

        return result.isSuccess() ? HttpResult.success(result.getMessage())
                : HttpResult.failure(result.getMessage());
    }

    @ResponseBody
    @PostMapping(value = "/contact/mobile/change2/{mobile}")
    public HttpResult sendCodeToNewMobile(@AuthenticationPrincipal(expression = "userId") Long userId,
                                          @AuthenticationPrincipal(expression = "clientId") String clientId,
                                          @PathVariable String mobile) {

        try {
            uiConfigService.validateMobileRule(mobile, userId, clientId);
        } catch (Exception ex) {
            return HttpResult.failure(ex.getMessage());
        }

        ClientSystem clientSystem = clientSystemService.findByClientId(clientId);
        String code = RandomCodeHelper.getNumberCode(6);
        String content = MessageFormat.format("{0}手机修改验证码：{1}", clientSystem.getName(), code);

        HttpResult result = smHistoryService.sendSm(clientId,
                mobile, content, FuncPointType.UPDATE_MOBILE, false, ReturnMode.SYNC_MODE);

        return result.isSuccess() ? HttpResult.success(result.getMessage())
                : HttpResult.failure(result.getMessage());
    }

    @RequestMapping("/contact/email")
    public String contactEmail(HttpServletRequest request, Model model,
                               @AuthenticationPrincipal(expression = "userId") Long userId,
                               @AuthenticationPrincipal(expression = "clientId") String clientId) {
        List<String> arrayList = new ArrayList<String>() {{
            add("/contact/email");
            add(EMAIL_VALIDATE_FLOW);
        }};
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null
                && authentication.getPrincipal() != null
                && authentication.getPrincipal() instanceof UserDetailsImpl) {
            if (((UserDetailsImpl) authentication.getPrincipal()).isEmailEnabled()) {
                arrayList.add(EMAIL_VALIDATE_FLOW);
            }
        }

        WorkFlowManager.clear(request);
        WorkFlowManager.insertFlowNodes(request, arrayList);
        return WorkFlowManager.next(request, "redirect:/");
    }


    @RequestMapping("/grant")
    public String grant(HttpServletRequest request, Model model, @PathVariable(value = "i", required = false) Integer index,
                        @AuthenticationPrincipal(expression = "userId") Long userId,
                        @AuthenticationPrincipal(expression = "clientId") String clientId) {
        List<TmtApproval> approvalList = approvalService.findByTmtUserId(userId, new String[]{"originSystem"});
        List<ClientSystem> clientsList = approvalList.stream().map(TmtApproval::getOriginSystem).distinct().collect(Collectors.toList());
        model.addAttribute("clients", clientsList);
        return "user/grant";
    }

    @ResponseBody
    @RequestMapping("/grant/data")
    public TableResponse grantData(TableRequest tableRequest, Model model) {
        return null;
    }

    @ResponseBody
    @RequestMapping("/scopes")
    public HttpResult clientApproval(@RequestParam String clientId, @AuthenticationPrincipal(expression = "username") String username) {
        ClientSystem client = clientService.findByClientId(clientId);
        List<Approval> approvals = approvalService.getApprovals(username, clientId);
        List<ScopeVo> clientScopes = clientService.findAllScopes().stream().filter(c -> client.getScope().contains(c.getValue())).collect(Collectors.toList());
        List<ScopeVo> list = new ArrayList<>();

        /**
         * 循环当前 client's scope
         */
        for (ScopeVo scope : clientScopes) {
            ScopeVo vo = new ScopeVo(scope.getTitle(), scope.getValue(), scope.getDescription(), scope.getSort(), scope.isDefaultSelected());
            Optional<Approval> optional = approvals.stream().filter(c -> Objects.equals(c.getScope(), scope.getValue())).findFirst();
            optional.ifPresent(approval -> vo.setSelected(approval.isApproved()));

            list.add(vo);
        }
        return HttpResult.success(null, list);
    }

    @ResponseBody
    @RequestMapping(value = "/scopes", method = RequestMethod.POST)
    public HttpResult changeApproval(@RequestParam String clientId, @RequestParam(value = "scopes[]") List<String> scopes, @AuthenticationPrincipal(expression = "username") String username) {
        if (scopes == null) {
            return HttpResult.failure("请选择有效的 scope");
        }

        ClientSystem clientSystem = clientService.findByClientId(clientId);
        // 避免赋予客户端没有允许的 scopes
        if (!clientSystem.getScope().containsAll(scopes)) {
            return HttpResult.failure("请选择有效的 scope");
        }

        List<ScopeVo> allScopes = clientService.findAllScopes();
        List<ScopeVo> clientScopes = allScopes.stream().filter(c -> clientSystem.getScope().contains(String.valueOf(c.getValue()))).collect(Collectors.toList());

        List<Approval> currentList = approvalService.getApprovals(username, clientId);
        approvalService.revokeApprovals(currentList);

        Date date = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, 1);
        Date expire = calendar.getTime();
        List<Approval> toAddList = new ArrayList<>();
        for (ScopeVo clientScope : clientScopes) {
            if (clientScope.isDefaultSelected() && !scopes.contains(String.valueOf(clientScope.getValue()))) {
                return HttpResult.failure("请选择有效的 scope");
            }

            Approval.ApprovalStatus status = scopes.contains(String.valueOf(clientScope.getValue())) ? Approval.ApprovalStatus.APPROVED : Approval.ApprovalStatus.DENIED;
            Approval approval = new Approval(username, clientSystem.getClientId(), clientScope.getValue().toString(), expire, status, date);
            toAddList.add(approval);
        }

        if (!toAddList.isEmpty()) {
            approvalService.addApprovals(toAddList);
        }

        return HttpResult.success();
    }


    @ResponseBody
    @RequestMapping(value = "/revoke", method = RequestMethod.POST)
    public HttpResult revokeApproval(@RequestParam String clientId, @AuthenticationPrincipal(expression = "username") String username) {
        List<Approval> toRemove = approvalService.getApprovals(username, clientId);
        approvalService.revokeApprovals(toRemove);

        return HttpResult.success();
    }

    @RequestMapping(value = "/safety", method = RequestMethod.GET)
    public String safety(HttpServletRequest request, Model model) {
        model.addAttribute("password", new PasswordVo());
        return "user/safety";
    }

    @RequestMapping(value = "/safety", method = RequestMethod.POST)
    public String editPassword(@Valid @ModelAttribute("password") PasswordVo passwordVo, BindingResult result, Model model, HttpServletRequest request,
                               @AuthenticationPrincipal(expression = "username") String username) {
        TmtUser user = userService.findByUsername(username);
        boolean match = userService.passwordMatch(user.getPassword(), passwordVo.getOriginalPassword());
        if (!match) {
            result.rejectValue("originalPassword", null, "原密码不匹配。");
            notify(model, Notify.notify(Notify.Level.DANGER, "原密码不匹配。", "请重新输入"));
        } else {
            userService.updatePassword(user.getId(), passwordVo.getNewPassword());
            notify(model, Notify.notify(Notify.Level.SUCCESS, "密码修改成功。", ""));
        }
        return "user/safety";
    }

    @RequestMapping("/history")
    public String history(HttpServletRequest request, Model model) {
        return "user/history";
    }

    @RequestMapping(value = "/scopetree", method = RequestMethod.GET)
    public String scopeTree(HttpServletRequest request, Model model) {
        return "scope_tree";
    }


    @ResponseBody
    @RequestMapping("/lock/release/{userId}")
    public HttpResult loginRelease(@PathVariable long userId) {
        TmtUser tmtUser = userService.findById(userId);
        loginAttemptService.loginSucceeded(tmtUser.getUsername());
        return HttpResult.success("用户解锁成功");
    }

}
